package io.gitee.cdw.sensitive.strategy;

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.DeserializerCache;
import com.fasterxml.jackson.databind.ser.SerializerCache;
import io.gitee.cdw.sensitive.strategy.loader.ISensitiveStrategyLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import io.gitee.cdw.sensitive.IJackson2ObjectMapperLoader;
import io.gitee.cdw.sensitive.model.FieldStrategyConfig;
import io.gitee.cdw.sensitive.model.SensitiveSerializerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Created by chendw on 2023/5/6 11:58.
 */


@Slf4j
public class StrategyManager {

    private Map<String, IStrategy> strategyMap;

    private final ConcurrentHashMap<String, FieldStrategyConfig> fieldStrategyConfigMap = new ConcurrentHashMap<>();

    private List<ObjectMapper> mappers;
    /**
     * 记录通过加载器加载的脱敏策略
     */
    private final Set<String> loadFieldStrategyConfigs = new HashSet<>();

    private static final String STRATEGY_CUSTOM = "custom";

    @Resource
    private List<IStrategy> strategyList;
    @Autowired(required = false)
    private IJackson2ObjectMapperLoader mapperLoader;

    @Autowired(required = false)
    private ISensitiveStrategyLoader strategyLoader;


    @PostConstruct
    public void init() {
        this.strategyMap = strategyList.stream().collect(Collectors.toMap(strategy -> strategy.id().toLowerCase(), Function.identity()));
        if (mapperLoader != null) {
            mappers = mapperLoader.get();
        }
        reload();
    }

    /**
     * 重新加载脱敏策略
     */
    public void reload() {
        log.debug("load field sensitive config [begin]");
        synchronized (fieldStrategyConfigMap) {
            // 删除通过加载器加载的策略
            for (Map.Entry<String, FieldStrategyConfig> entry : fieldStrategyConfigMap.entrySet()) {
                if (loadFieldStrategyConfigs.contains(entry.getKey())) {
                    fieldStrategyConfigMap.remove(entry.getKey());
                }
            }

            if (strategyLoader != null) {
                loadFieldStrategyConfigs.clear();
                List<FieldStrategyConfig> configs = strategyLoader.load();
                for (FieldStrategyConfig config : configs) {
                    if (StringUtils.isNotBlank(config.getPattern())) {
                        addPatternConfig(config.getField(), config.getPattern());
                    } else {

                        addConfig(config.getField(), config);
                    }
                    loadFieldStrategyConfigs.add(config.getField());
                }
            }
        }
        if (mappers != null) {
            for (ObjectMapper mapper : mappers) {
                invalidateJacksonCaches(mapper);
            }
            log.debug("invalidate Jackson Caches");
        }
        log.debug("load field sensitive config [end]");
    }

    private IStrategy getStrategy(String id) {
        return strategyMap.get(id.toLowerCase());
    }

    private FieldStrategyConfig getFieldStrategyConfig(String field) {
        return fieldStrategyConfigMap.get(field.toLowerCase());
    }

    private void put(String field, FieldStrategyConfig config) {
        fieldStrategyConfigMap.put(field.toLowerCase(), config);
    }

    private void remove(String field) {
        fieldStrategyConfigMap.remove(field.toLowerCase());
    }

    public void addConfig(String field, String type) {
        addConfig(field, type, 0, 0, null);
    }

    public void addReqConfig(String name, String field, String type) {
        addConfig(name, field, type, 0, 0, null);
    }

    private void addConfig(String field, String type, int left, int right, String pattern) {
        addConfig(null, field, type, left, right, pattern);
    }


    private void addConfig(String name, String field, String type, int left, int right, String pattern) {
        FieldStrategyConfig config = new FieldStrategyConfig();
        String strategyType = StringUtils.isBlank(type) ? STRATEGY_CUSTOM : type;
        config.setType(strategyType);
        config.setLeft(left);
        config.setRight(right);
        config.setPattern(pattern);
        config.setName(name);

        addConfig(field, config);
    }

    private void addConfig(String field, FieldStrategyConfig config) {
        put(field, config);
    }

    /**
     * 全局脱敏策略
     *
     * @param field 需要脱敏的类字段
     * @param type  脱敏策略
     * @param left  左边显示位数
     * @param right 右边显示位数
     */
    public void addConfig(String field, String type, int left, int right) {
        addConfig(field, type, left, right, null);
    }

    /**
     * 特定接口脱敏策略
     *
     * @param name  接口名称
     * @param field 需要脱敏的类字段格式：类名#成员属性名(如：com.example.entity.SensitiveInfo#Name)
     * @param type  脱敏策略
     * @param left  左边显示位数
     * @param right 右边显示位数
     */
    public void addReqConfig(String name, String field, String type, int left, int right) {
        addConfig(name, field, type, left, right, null);
    }

    public void addReqConfig(String name, String field, int left, int right) {
        addConfig(name, field, STRATEGY_CUSTOM, left, right, null);
    }

    public void addConfig(String key, int left, int right) {
        addConfig(key, STRATEGY_CUSTOM, left, right, null);
    }

    public void addReqPatternConfig(String name, String field, String pattern) {
        addConfig(name, field, STRATEGY_CUSTOM, 0, 0, pattern);
    }

    public void deleteConfig(String field) {
        remove(field);
    }

    public void addPatternConfig(String field, String pattern) {
        addConfig(field, STRATEGY_CUSTOM, 0, 0, pattern);
    }

    public SensitiveSerializerConfig getSensitiveSerializerConfig(String field) {
        FieldStrategyConfig config = getFieldStrategyConfig(field);

        SensitiveSerializerConfig serializerConfig = new SensitiveSerializerConfig();
        if (StringUtils.isNotBlank(config.getType())) {
            serializerConfig.setStrategy(getStrategy(config.getType()));
        }
        serializerConfig.setConfig(config);
        return serializerConfig;
    }

    public boolean contains(String field) {
        return fieldStrategyConfigMap.containsKey(field.toLowerCase());
    }

    private void invalidateJacksonCaches(ObjectMapper mapper) {
        try {
            log.trace("Flushing Jackson serializer cache");
            SerializerProvider serializerProvider = mapper.getSerializerProvider();
            Field serializerCacheField = serializerProvider.getClass().getSuperclass().getSuperclass().getDeclaredField("_serializerCache");
            ReflectionUtils.makeAccessible(serializerCacheField);
            SerializerCache serializerCache = (SerializerCache) serializerCacheField.get(serializerProvider);
            Method serializerCacheFlushMethod = SerializerCache.class.getDeclaredMethod("flush");
            serializerCacheFlushMethod.invoke(serializerCache);

            log.trace("Flushing Jackson deserializer cache");
            DeserializationContext deserializationContext = mapper.getDeserializationContext();
            Field deSerializerCacheField = deserializationContext.getClass().getSuperclass().getSuperclass().getDeclaredField("_cache");
            ReflectionUtils.makeAccessible(deSerializerCacheField);
            DeserializerCache deSerializerCache = (DeserializerCache) deSerializerCacheField.get(deserializationContext);
            Method deSerializerCacheFlushMethod = DeserializerCache.class.getDeclaredMethod("flushCachedDeserializers");
            deSerializerCacheFlushMethod.invoke(deSerializerCache);

        } catch (Exception e) {
            log.error("Could not hot reload Jackson class!", e);
        }
    }

    public List<ObjectMapper> getMapper() {
        return mappers;
    }
}
